// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/ppapi_plugin/broker_process_dispatcher.h"

#include <stddef.h>

#include <memory>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/debug/dump_without_crashing.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/child/child_process.h"
#include "ppapi/c/pp_bool.h"
#include "ppapi/c/private/ppp_flash_browser_operations.h"
#include "ppapi/proxy/ppapi_messages.h"

namespace content {
namespace {

    // How long we wait before releasing the broker process.
    const int kBrokerReleaseTimeSeconds = 30;

    std::string ConvertPluginDataPath(const base::FilePath& plugin_data_path)
    {
        // The string is always 8-bit, convert on Windows.
#if defined(OS_WIN)
        return base::WideToUTF8(plugin_data_path.value());
#else
        return plugin_data_path.value();
#endif
    }

    struct GetPermissionSettingsContext {
        GetPermissionSettingsContext(
            const base::WeakPtr<BrokerProcessDispatcher> in_dispatcher,
            uint32_t in_request_id)
            : dispatcher(in_dispatcher)
            , request_id(in_request_id)
        {
        }

        base::WeakPtr<BrokerProcessDispatcher> dispatcher;
        uint32_t request_id;
    };

    void GetPermissionSettingsCallback(
        void* user_data,
        PP_Bool success,
        PP_Flash_BrowserOperations_Permission default_permission,
        uint32_t site_count,
        const PP_Flash_BrowserOperations_SiteSetting sites[])
    {
        std::unique_ptr<GetPermissionSettingsContext> context(
            reinterpret_cast<GetPermissionSettingsContext*>(user_data));

        if (!context->dispatcher.get())
            return;

        ppapi::FlashSiteSettings site_vector;
        if (success) {
            site_vector.reserve(site_count);
            for (uint32_t i = 0; i < site_count; ++i) {
                if (!sites[i].site) {
                    success = PP_FALSE;
                    break;
                }
                site_vector.push_back(
                    ppapi::FlashSiteSetting(sites[i].site, sites[i].permission));
            }

            if (!success)
                site_vector.clear();
        }
        context->dispatcher->OnGetPermissionSettingsCompleted(
            context->request_id, PP_ToBool(success), default_permission, site_vector);
    }

} // namespace

BrokerProcessDispatcher::BrokerProcessDispatcher(
    PP_GetInterface_Func get_plugin_interface,
    PP_ConnectInstance_Func connect_instance,
    bool peer_is_browser)
    : ppapi::proxy::BrokerSideDispatcher(connect_instance)
    , get_plugin_interface_(get_plugin_interface)
    , flash_browser_operations_1_3_(NULL)
    , flash_browser_operations_1_2_(NULL)
    , flash_browser_operations_1_0_(NULL)
    , peer_is_browser_(peer_is_browser)
{
    if (get_plugin_interface) {
        flash_browser_operations_1_0_ = static_cast<const PPP_Flash_BrowserOperations_1_0*>(
            get_plugin_interface_(PPP_FLASH_BROWSEROPERATIONS_INTERFACE_1_0));

        flash_browser_operations_1_2_ = static_cast<const PPP_Flash_BrowserOperations_1_2*>(
            get_plugin_interface_(PPP_FLASH_BROWSEROPERATIONS_INTERFACE_1_2));

        flash_browser_operations_1_3_ = static_cast<const PPP_Flash_BrowserOperations_1_3*>(
            get_plugin_interface_(PPP_FLASH_BROWSEROPERATIONS_INTERFACE_1_3));
    }
}

BrokerProcessDispatcher::~BrokerProcessDispatcher()
{
    DVLOG(1) << "BrokerProcessDispatcher::~BrokerProcessDispatcher()";
    // Don't free the process right away. This timer allows the child process
    // to be re-used if the user rapidly goes to a new page that requires this
    // plugin. This is the case for common plugins where they may be used on a
    // source and destination page of a navigation. We don't want to tear down
    // and re-start processes each time in these cases.
    process_ref_.ReleaseWithDelay(
        base::TimeDelta::FromSeconds(kBrokerReleaseTimeSeconds));
}

bool BrokerProcessDispatcher::OnMessageReceived(const IPC::Message& msg)
{
    if (BrokerSideDispatcher::OnMessageReceived(msg))
        return true;

    if (!peer_is_browser_) {
        // We might want to consider killing the peer instead is we see problems in
        // the future.
        if (msg.type() == PpapiMsg_GetSitesWithData::ID || msg.type() == PpapiMsg_ClearSiteData::ID || msg.type() == PpapiMsg_DeauthorizeContentLicenses::ID || msg.type() == PpapiMsg_GetPermissionSettings::ID || msg.type() == PpapiMsg_SetDefaultPermission::ID || msg.type() == PpapiMsg_SetSitePermission::ID) {
            base::debug::DumpWithoutCrashing();
        }
        return false;
    }

    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(BrokerProcessDispatcher, msg)
    IPC_MESSAGE_HANDLER(PpapiMsg_GetSitesWithData, OnGetSitesWithData)
    IPC_MESSAGE_HANDLER(PpapiMsg_ClearSiteData, OnClearSiteData)
    IPC_MESSAGE_HANDLER(PpapiMsg_DeauthorizeContentLicenses,
        OnDeauthorizeContentLicenses)
    IPC_MESSAGE_HANDLER(PpapiMsg_GetPermissionSettings,
        OnGetPermissionSettings)
    IPC_MESSAGE_HANDLER(PpapiMsg_SetDefaultPermission, OnSetDefaultPermission)
    IPC_MESSAGE_HANDLER(PpapiMsg_SetSitePermission, OnSetSitePermission)
    IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    return handled;
}

void BrokerProcessDispatcher::OnGetPermissionSettingsCompleted(
    uint32_t request_id,
    bool success,
    PP_Flash_BrowserOperations_Permission default_permission,
    const ppapi::FlashSiteSettings& sites)
{
    Send(new PpapiHostMsg_GetPermissionSettingsResult(
        request_id, success, default_permission, sites));
}

void BrokerProcessDispatcher::OnGetSitesWithData(
    uint32_t request_id,
    const base::FilePath& plugin_data_path)
{
    std::vector<std::string> sites;
    GetSitesWithData(plugin_data_path, &sites);
    Send(new PpapiHostMsg_GetSitesWithDataResult(request_id, sites));
}

void BrokerProcessDispatcher::OnClearSiteData(
    uint32_t request_id,
    const base::FilePath& plugin_data_path,
    const std::string& site,
    uint64_t flags,
    uint64_t max_age)
{
    Send(new PpapiHostMsg_ClearSiteDataResult(
        request_id, ClearSiteData(plugin_data_path, site, flags, max_age)));
}

void BrokerProcessDispatcher::OnDeauthorizeContentLicenses(
    uint32_t request_id,
    const base::FilePath& plugin_data_path)
{
    Send(new PpapiHostMsg_DeauthorizeContentLicensesResult(
        request_id, DeauthorizeContentLicenses(plugin_data_path)));
}

void BrokerProcessDispatcher::OnGetPermissionSettings(
    uint32_t request_id,
    const base::FilePath& plugin_data_path,
    PP_Flash_BrowserOperations_SettingType setting_type)
{
    if (flash_browser_operations_1_3_) {
        std::string data_str = ConvertPluginDataPath(plugin_data_path);
        // The GetPermissionSettingsContext object will be deleted in
        // GetPermissionSettingsCallback().
        flash_browser_operations_1_3_->GetPermissionSettings(
            data_str.c_str(), setting_type, &GetPermissionSettingsCallback,
            new GetPermissionSettingsContext(AsWeakPtr(), request_id));
        return;
    }

    if (flash_browser_operations_1_2_) {
        std::string data_str = ConvertPluginDataPath(plugin_data_path);
        // The GetPermissionSettingsContext object will be deleted in
        // GetPermissionSettingsCallback().
        flash_browser_operations_1_2_->GetPermissionSettings(
            data_str.c_str(), setting_type, &GetPermissionSettingsCallback,
            new GetPermissionSettingsContext(AsWeakPtr(), request_id));
        return;
    }

    OnGetPermissionSettingsCompleted(
        request_id, false, PP_FLASH_BROWSEROPERATIONS_PERMISSION_DEFAULT,
        ppapi::FlashSiteSettings());
    return;
}

void BrokerProcessDispatcher::OnSetDefaultPermission(
    uint32_t request_id,
    const base::FilePath& plugin_data_path,
    PP_Flash_BrowserOperations_SettingType setting_type,
    PP_Flash_BrowserOperations_Permission permission,
    bool clear_site_specific)
{
    Send(new PpapiHostMsg_SetDefaultPermissionResult(
        request_id,
        SetDefaultPermission(plugin_data_path, setting_type, permission,
            clear_site_specific)));
}

void BrokerProcessDispatcher::OnSetSitePermission(
    uint32_t request_id,
    const base::FilePath& plugin_data_path,
    PP_Flash_BrowserOperations_SettingType setting_type,
    const ppapi::FlashSiteSettings& sites)
{
    Send(new PpapiHostMsg_SetSitePermissionResult(
        request_id, SetSitePermission(plugin_data_path, setting_type, sites)));
}

void BrokerProcessDispatcher::GetSitesWithData(
    const base::FilePath& plugin_data_path,
    std::vector<std::string>* site_vector)
{
    std::string data_str = ConvertPluginDataPath(plugin_data_path);
    if (flash_browser_operations_1_3_) {
        char** sites = NULL;
        flash_browser_operations_1_3_->GetSitesWithData(data_str.c_str(), &sites);
        if (!sites)
            return;

        for (size_t i = 0; sites[i]; ++i)
            site_vector->push_back(sites[i]);

        flash_browser_operations_1_3_->FreeSiteList(sites);
    }
}

bool BrokerProcessDispatcher::ClearSiteData(
    const base::FilePath& plugin_data_path,
    const std::string& site,
    uint64_t flags,
    uint64_t max_age)
{
    std::string data_str = ConvertPluginDataPath(plugin_data_path);
    if (flash_browser_operations_1_3_) {
        flash_browser_operations_1_3_->ClearSiteData(
            data_str.c_str(), site.empty() ? NULL : site.c_str(), flags, max_age);
        return true;
    }

    // TODO(viettrungluu): Remove this (and the 1.0 interface) sometime after M21
    // goes to Stable.
    if (flash_browser_operations_1_2_) {
        flash_browser_operations_1_2_->ClearSiteData(
            data_str.c_str(), site.empty() ? NULL : site.c_str(), flags, max_age);
        return true;
    }

    if (flash_browser_operations_1_0_) {
        flash_browser_operations_1_0_->ClearSiteData(
            data_str.c_str(), site.empty() ? NULL : site.c_str(), flags, max_age);
        return true;
    }

    return false;
}

bool BrokerProcessDispatcher::DeauthorizeContentLicenses(
    const base::FilePath& plugin_data_path)
{
    if (flash_browser_operations_1_3_) {
        std::string data_str = ConvertPluginDataPath(plugin_data_path);
        return PP_ToBool(flash_browser_operations_1_3_->DeauthorizeContentLicenses(
            data_str.c_str()));
    }

    if (flash_browser_operations_1_2_) {
        std::string data_str = ConvertPluginDataPath(plugin_data_path);
        return PP_ToBool(flash_browser_operations_1_2_->DeauthorizeContentLicenses(
            data_str.c_str()));
    }

    return false;
}

bool BrokerProcessDispatcher::SetDefaultPermission(
    const base::FilePath& plugin_data_path,
    PP_Flash_BrowserOperations_SettingType setting_type,
    PP_Flash_BrowserOperations_Permission permission,
    bool clear_site_specific)
{
    if (flash_browser_operations_1_3_) {
        std::string data_str = ConvertPluginDataPath(plugin_data_path);
        return PP_ToBool(flash_browser_operations_1_3_->SetDefaultPermission(
            data_str.c_str(), setting_type, permission,
            PP_FromBool(clear_site_specific)));
    }

    if (flash_browser_operations_1_2_) {
        std::string data_str = ConvertPluginDataPath(plugin_data_path);
        return PP_ToBool(flash_browser_operations_1_2_->SetDefaultPermission(
            data_str.c_str(), setting_type, permission,
            PP_FromBool(clear_site_specific)));
    }

    return false;
}

bool BrokerProcessDispatcher::SetSitePermission(
    const base::FilePath& plugin_data_path,
    PP_Flash_BrowserOperations_SettingType setting_type,
    const ppapi::FlashSiteSettings& sites)
{
    if (sites.empty())
        return true;

    std::string data_str = ConvertPluginDataPath(plugin_data_path);
    std::unique_ptr<PP_Flash_BrowserOperations_SiteSetting[]> site_array(
        new PP_Flash_BrowserOperations_SiteSetting[sites.size()]);

    for (size_t i = 0; i < sites.size(); ++i) {
        site_array[i].site = sites[i].site.c_str();
        site_array[i].permission = sites[i].permission;
    }

    if (flash_browser_operations_1_3_) {
        PP_Bool result = flash_browser_operations_1_3_->SetSitePermission(
            data_str.c_str(), setting_type,
            static_cast<uint32_t>(sites.size()), site_array.get());

        return PP_ToBool(result);
    }

    if (flash_browser_operations_1_2_) {
        PP_Bool result = flash_browser_operations_1_2_->SetSitePermission(
            data_str.c_str(), setting_type,
            static_cast<uint32_t>(sites.size()), site_array.get());

        return PP_ToBool(result);
    }

    return false;
}

} // namespace content
